// Server.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <Winsock2.h>
#include <process.h>
#include "conio.h"

#pragma comment(lib, "ws2_32.lib")

#define QLEN 5
#define STRLEN 4096
#define SERVPORT 5400
#define WSVERS MAKEWORD(2, 2)
//#define _WINSOCK_DEPRECATED_NO_WARNINGS

#define USER_NOT_EXISTS 400
#define INCORRECT_PASSWORD 401

#define MAXCLNT 64
#define CLIUSERLEN 8
HANDLE hThread[MAXCLNT];
int	   cln_count = 0;
struct SockInfo {
	SOCKET sock;
	int userId;
} cliSock[MAXCLNT];
struct User {
	int valid;
	char username[CLIUSERLEN + 1];
	char password[CLIUSERLEN + 1];
	int online;
	struct sockaddr_in addr;
} CliUser[MAXCLNT];

unsigned __stdcall send_to_all(void * linksock);

void errExit(const char* errStr, int num = 0) {
	printf(errStr, num);
	return;
}

void testSend(int messageLen) {
	switch (messageLen) {
	case SOCKET_ERROR: errExit("send() failed: %d\n", WSAGetLastError()); break;
	case 0: errExit("send() failed: %d\n", WSAGetLastError()); break;
	default: break;//printf("send() successfully. \n", messageLen);
	}
	return;
}

void initUser() {
	CliUser[0].valid = 1;
	strncpy(CliUser[0].username, "test", 5);
	strncpy(CliUser[0].password, "test", 5);
	CliUser[0].online = 0;
	CliUser[1].valid = 1;
	strncpy(CliUser[1].username, "chenxf", 7);
	strncpy(CliUser[1].password, "chenxf", 7);
	CliUser[1].online = 0;
	CliUser[2].valid = 1;
	strncpy(CliUser[2].username, "remote", 7);
	strncpy(CliUser[2].password, "remote", 7);
	CliUser[2].online = 0;
	for (int i = 3; i < MAXCLNT; ++i) {
		CliUser[i].valid = 0;
	}
	return;
}

int findUser(char * message) {
	for (int i = 0; i < MAXCLNT; ++i) {
		if (CliUser[i].valid) {
			if (strstr(message, CliUser[i].username) != NULL)
				return i;
		}
	}

	return MAXCLNT + 1;
}

int userLogIn(SOCKET linksock, struct sockaddr_in addr) {
	SOCKET lsock = linksock;
	char message[STRLEN];
	char sendmsg[STRLEN];
	struct sockaddr_in cln_addr = addr;
	int cln_addr_len = sizeof(cln_addr);
	int messageLen;

	// resv username
	memset(&message, 0, sizeof(message));
	int recvlen = recv(lsock, message, sizeof(message), 0);

	if (recvlen == 0 || recvlen == SOCKET_ERROR) {
		memset(&sendmsg, 0, sizeof(sendmsg));
		sprintf(sendmsg, "user from IP: %s port: %d log in failed. \n",
			inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
		printf("%s", sendmsg);
		return -1;
	}
	else if (recvlen > 0) {
		// get username
		// message[recvlen] = '\0';
		int userId = findUser(message); // maybe unsafe
		if (userId < MAXCLNT) {

			// find the user, request for password
			memset(&sendmsg, 0, sizeof(sendmsg));
			sprintf(sendmsg, "user %s from IP: %s port: %d. Please Input password: ",
				CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
			messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
			testSend(messageLen);

			// get password
			memset(&message, 0, sizeof(message));
			int pwdLen = recv(lsock, message, sizeof(message), 0);

			if (pwdLen == 0 || pwdLen == SOCKET_ERROR) {
				// get password error
				memset(&sendmsg, 0, sizeof(sendmsg));
				sprintf(sendmsg, "user %s from IP: %s port: %d log in failed: get-password error. \n",
					CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
				printf("%s", sendmsg);
				return -1;
			}
			else if (pwdLen > 0) {
				// got password, confirm it
				// message[pwdLen] = '\0';
				if (strstr(message, CliUser[userId].password) != NULL) {
					// correct password
					memset(&sendmsg, 0, sizeof(sendmsg));
					sprintf(sendmsg, "user %s from IP: %s port: %d log in. \n",
						CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
					printf("%s", sendmsg);

					// log in success, reply the user
					messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
					testSend(messageLen);

					// change user status
					CliUser[userId].online = 1;
					CliUser[userId].addr.sin_family = AF_INET;
					CliUser[userId].addr.sin_addr = cln_addr.sin_addr;
					CliUser[userId].addr.sin_port = cln_addr.sin_port;
					return userId;
				}
				else {
					// wrong passeord
					memset(&sendmsg, 0, sizeof(sendmsg));
					sprintf(sendmsg, "incorrect password: user %s from IP: %s port: %d log in failed. \n",
						CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
					printf("%s", sendmsg);
					// reply INCORRECT_PASSWORD(401)
					memset(&sendmsg, 0, sizeof(sendmsg));
					sprintf(sendmsg, "%d", INCORRECT_PASSWORD);
					messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
					testSend(messageLen);
					return -1;
				}
			}
		}
		else {
			// username not found
			memset(&sendmsg, 0, sizeof(sendmsg));
			sprintf(sendmsg, "user not exists: user %s from IP: %s port: %d log in failed. \n",
				message, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
			printf("%s", sendmsg);
			// reply USER_NOT_EXISTS(400)
			memset(&sendmsg, 0, sizeof(sendmsg));
			sprintf(sendmsg, "%d", USER_NOT_EXISTS);
			messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
			testSend(messageLen);
			return -1;
		}
	}

	return -1;
}

int getSockUserId(SOCKET linksock) {
	for (int i = 0; i < MAXCLNT; ++i) {
		if (linksock == cliSock[i].sock)
			return cliSock[i].userId;
	}
	return -1;
}

void writeUserListToBuf(char * buf, int num) {
	strcpy(buf, "online user list: \n");
	int i;
	for (i = 0; i < num; ++i) {
		if (CliUser[cliSock[i].userId].online) {
			strncat(buf, CliUser[cliSock[i].userId].username, CLIUSERLEN);
			strcat(buf, "\n");
		}
	}
	char iToStr[4];
	sprintf(iToStr, "%d", i);
	strcat(buf, iToStr);
	strcat(buf, " in total. \n");

	return;
}

int main(int argc, char **argv) {
	initUser();
	struct sockaddr_in localSockIn, fromSockIn;
	SOCKET masterSock;
	WSADATA wsadata;
	int addrLen;
	char rBuf[STRLEN], wBuf[STRLEN];
	int messageLen;
	if (WSAStartup(WSVERS, &wsadata) != 0)
		errExit("WSAStartup failed\n");

	// create socket
	masterSock = socket(PF_INET, SOCK_STREAM, 0);
	if (masterSock == INVALID_SOCKET) {
		errExit("failed socket, error number: %d\n", WSAGetLastError());
	}

	// server address
	localSockIn.sin_family = AF_INET;
	localSockIn.sin_port = htons(SERVPORT);
	localSockIn.sin_addr.s_addr = htonl(INADDR_ANY);

	// binding
	if (bind(masterSock, (SOCKADDR*)&localSockIn, sizeof(localSockIn)) == SOCKET_ERROR) {
		errExit("bind() failed: %d\n", WSAGetLastError());
	}

	// listen
	if (listen(masterSock, QLEN) == SOCKET_ERROR) {
		errExit("listen() failed: %d\n", WSAGetLastError());
	}

	// client address length
	addrLen = sizeof(fromSockIn);

	// wait for connection from client...
	while (true) {
		if (cln_count < MAXCLNT) {
			printf("port %d is waiting for connection from %s...\n",
				ntohs(localSockIn.sin_port), inet_ntoa(localSockIn.sin_addr));

			cliSock[cln_count].sock = accept(masterSock, (SOCKADDR*)&fromSockIn, &addrLen);
			if (cliSock[cln_count].sock == INVALID_SOCKET) {
				errExit("eccept() failed: %d\n", WSAGetLastError());
			}
			printf("accept a client whose IP is %s port %d. \n",
				inet_ntoa(fromSockIn.sin_addr), ntohs(fromSockIn.sin_port));

			// struct sockaddr_in clnAddr;
			// int clnAddrLen;
			// memset(&clnAddr, 0, sizeof(clnAddr));
			// int ret = getpeername(lsock, (struct sockaddr*)&cln_addr, &cln_addr_len);

			// the cli log in
			int userId = userLogIn(cliSock[cln_count].sock, fromSockIn);
			if (userId < 0) {
				// log in failed
				continue;
			}
			cliSock[cln_count].userId = userId;

			// send user list to new cli
			memset(wBuf, 0, sizeof(wBuf));
			writeUserListToBuf(wBuf, cln_count);
			messageLen = send(cliSock[cln_count].sock, wBuf, strlen(wBuf), 0);
			testSend(messageLen); 
			printf("%s", wBuf);

			// send to all clis, including latest one
			memset(wBuf, 0, sizeof(wBuf));
			sprintf(wBuf, "user %s from IP: %s port number: %d log in. \n",
				CliUser[userId].username, inet_ntoa(fromSockIn.sin_addr), fromSockIn.sin_port);
			for (int i = 0; i <= cln_count; ++i) {
				// fputs("Input message: ", stdout);
				// fgets(wBuf, STRLEN, stdin);
				// scanf_s("%s", wBuf, sizeof(wBuf));
				messageLen = send(cliSock[i].sock, wBuf, strlen(wBuf), 0);
				testSend(messageLen);
			}
			// take the socket into thread pool
			hThread[cln_count] = (HANDLE)_beginthreadex(NULL, 0, &send_to_all, (void *)&cliSock[cln_count], 0, NULL);
			cln_count++;
		}
		else {
			printf("There are too much clis online.\n");
			break;
		}
	}

	WaitForMultipleObjects(cln_count, hThread, TRUE, INFINITE);
	closesocket(masterSock);
	// close thread
	for (int i = 0; i < cln_count; i++) {
		CloseHandle(hThread[i]);
	}
	WSACleanup();

	return 0;
}

unsigned __stdcall send_to_all(void * linksock) {
	int		sock_index;
	struct  sockaddr_in cln_addr;
	int		cln_addr_len;
	char	message[STRLEN];
	char	sendmsg[STRLEN + 100];
	SOCKET  lsock = *((SOCKET*)linksock);
	int recvlen, messageLen;

	while (true) {
		memset(&message, 0, sizeof(message));
		recvlen = recv(lsock, message, STRLEN, 0);

		// get sock_addr
		int userId = getSockUserId(lsock);
		if (lsock < 0) break;
		memset(&cln_addr, 0, sizeof(cln_addr));
		cln_addr = CliUser[userId].addr;
		cln_addr_len = sizeof(cln_addr);

		if (recvlen == 0 || recvlen == SOCKET_ERROR) {
			// force downline
			memset(&sendmsg, 0, sizeof(sendmsg));
			sprintf(sendmsg, "user %s from IP: %s port: %d log out. \n",
				CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
			printf("%s", sendmsg);
			CliUser[userId].online = 0;
			for (int i = 0; i < cln_count; i++) {
				if (cliSock[i].sock != lsock) {
					messageLen = send(cliSock[i].sock, sendmsg, strlen(sendmsg), 0);
					testSend(messageLen);
				}
				else
					sock_index = i;
			}
			break;
		}
		else if (recvlen > 0) {
			message[recvlen] = '\0';

			if (strstr(message, "quit") != NULL) {
				// quit
				memset(&sendmsg, 0, sizeof(sendmsg));
				sprintf(sendmsg, "user %s from IP: %s port: %d log out. \n",
					CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
				printf("%s", sendmsg);
				CliUser[userId].online = 0;
				// inform all users
				for (int i = 0; i < cln_count; i++) {
					if (cliSock[i].sock != lsock) {
						messageLen = send(cliSock[i].sock, sendmsg, strlen(sendmsg), 0);
						testSend(messageLen);
					}
					else
						sock_index = i;
				}

				break;
			}
			else {
				// normal message
				memset(&sendmsg, 0, sizeof(sendmsg));
				sprintf(sendmsg, "user %s from IP: %s port: %d say: %s",
					CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port, message);
				printf("%s", sendmsg);

				// send to all users
				for (int i = 0; i < cln_count; i++) {
					messageLen = send(cliSock[i].sock, sendmsg, strlen(sendmsg), 0);
					testSend(messageLen);
				}
			}
		}
	}

	// move the sockets behind the log-out one forward
	for (int i = sock_index; i < cln_count - 1; i++) {
		cliSock[i].sock = cliSock[i + 1].sock;
		cliSock[i].userId = cliSock[i + 1].userId;
	}
	cln_count--;

	closesocket(lsock);
	return 0;
}
